dwnpm
Version:
Decentralized Registry Package Manager (DRPM) helps developers publish, install, find and manage Decentralized Packages (DPKs) published to Decentralized Web Nodes (DWNs). DRPM does this by looking up a Decentralized Identifier (DID) to find its DID docum
229 lines (202 loc) • 8.02 kB
text/typescript
import { createHash } from 'crypto';
import { Request } from 'express';
import { createReadStream, createWriteStream } from 'fs';
import { ensureDir, exists } from 'fs-extra';
import { access, readFile, writeFile } from 'fs/promises';
import { join } from 'path';
import { pipeline } from 'stream/promises';
import {
DRPM_REGISTRY_DIR,
DRPM_VERSION_PREFIXES
} from '../config.js';
import { Logger } from '../utils/logger.js';
import {
PrefixResponse,
RegistryGetDpkPath,
RegistryResponse,
RegistrySaveDpkData,
RouteFailureParams,
RouteSuccessParams
} from '../utils/types.js';
export class RegistryUtils {
// Saves the tarball file to path $HOME/.drpm/registry/name/version/name-version.tgz
static async saveDpkTarball({ name, version, data }: RegistrySaveDpkData): Promise<boolean | string> {
const dpkAtVersionPath = this.getDpkVersionPath({name, version});
try {
await this.ensureDpkDir(dpkAtVersionPath);
Logger.info(`Ensured dir at ${dpkAtVersionPath}`);
} catch (error: any) {
Logger.error(`DRegistry: Failed to ensure dir at ${dpkAtVersionPath}`, error);
return false;
}
const dpkTarballPath = this.getDpkTarballPath({name, version});
try {
Logger.log(`Saving tarball to ${dpkTarballPath} ...`);
await pipeline(data, createWriteStream(dpkTarballPath));
return dpkTarballPath;
} catch (error) {
Logger.error(`DRegistry: Failed to save dpk tarball to ${dpkTarballPath} `, error);
return false;
}
}
// Saves the metadata file to path $HOME/.drpm/registry/name/version/metadata.json
static async saveDpkMetadata({ name, version, data }: RegistrySaveDpkData): Promise<boolean> {
const dpkAtVersionPath = this.getDpkVersionPath({name, version});
try {
await this.ensureDpkDir(dpkAtVersionPath);
} catch (error: any) {
Logger.error(`DRegistry: Failed to ensure dir at ${dpkAtVersionPath}`, error);
return false;
}
const dpkMetadataPath = this.getDpkMetadataPath({name, version});
try {
await writeFile(dpkMetadataPath, JSON.stringify(data, null, 2));
Logger.info(`Saved metadata to ${dpkMetadataPath}`);
return true;
} catch (error: any) {
Logger.error(`DRegistry: Failed to save dpk metadata to ${dpkMetadataPath} `, error);
return false;
}
}
// Ensure the path to the $HOME/.drpm/registry/{dpk, dpk/version} directory exists
static async ensureDpkDir(path: string): Promise<any> {
try {
await ensureDir(path);
Logger.info(`Ensured dir at ${path}`);
return true;
} catch (error: any) {
Logger.error(`DRegistry: Failed to ensure dir at ${path}`, error);
throw error;
}
}
// Get the path to the $HOME/.drpm/registry/name directory
static getDpkPath({name}: {name: string}): string {
Logger.info(`Getting dpk dir path for ${name}`);
return join(DRPM_REGISTRY_DIR, name);
};
// Get the path to the $HOME/.drpm/registry/name directory
static getDpkLatestPath({name}: {name: string}): string {
Logger.info(`Getting dpk dir path for ${name}`);
return join(DRPM_REGISTRY_DIR, name, 'latest');
};
// Get the path to the $HOME/.drpm/registry/name/version directory
static getDpkVersionPath({name, version}: RegistryGetDpkPath): string {
Logger.info(`Getting dpkVersion dir path for ${name}@${version}`);
return join(DRPM_REGISTRY_DIR, name, version);
};
// Get the path to the $HOME/.drpm/registry/name/version/metadata.json file
// e.g. /Users/username/.drpm/registry/tool5/6.1.0/metadata.json
static getDpkMetadataPath({name, version}: RegistryGetDpkPath): string {
Logger.info(`Getting metadata path for ${name}@${version}`);
return join(DRPM_REGISTRY_DIR, name, version, 'metadata.json');
};
// Get the path to the $HOME/.drpm/registry/dpk/version/dpk-version.tgz file
// e.g. /Users/username/.drpm/registry/tool5/6.1.0/tool5-6.1.0.tgz
static getDpkTarballPath({name, version}: RegistryGetDpkPath): string {
Logger.info(`Getting tarball path for ${name}@${version}`);
return join(DRPM_REGISTRY_DIR, name, version, `${name}-${version}.tgz`);
};
static async saveMetadataToPath({path, metadata}: {path: string; metadata: any}): Promise<boolean> {
try {
await writeFile(path, JSON.stringify(metadata, null, 2));
Logger.info(`Saved metadata to ${path}`);
return true;
} catch (error: any) {
Logger.error(`DRegistry: Failed to save metadata to ${path} `, error);
return false;
}
}
static async saveTarballToPath({path, tarball}: {path: string; tarball: any}): Promise<boolean> {
try {
await pipeline(tarball, createWriteStream(path));
Logger.info(`Saved tarball to ${path}`);
return true;
} catch (error: any) {
Logger.error(`DRegistry: Failed to save tarball to ${path} `, error);
return false;
}
}
static async loadDpkMetadata(path: string): Promise<any> {
try {
if(!await exists(path)) {
Logger.info(`DRegistry: metadata.json does not exist at path ${path}`);
return null;
}
await access(path);
const metadata = JSON.parse(await readFile(path, 'utf8'));
return metadata;
} catch (error: any) {
Logger.error(`DRegistry: metadata.json does not exist at path ${path}`, error);
throw error;
}
};
static async loadDpkTarball(path: string): Promise<any> {
try {
if(!await exists(path)) {
Logger.info(`DRegistry: tarball does not exist at path ${path}`);
return null;
}
await access(path);
return path;
} catch (error: any) {
Logger.error(`DRegistry: tarball does not exist at path ${path}`, error);
throw error;
}
};
static createMetadata(metadata: any): any {
const {name, version} = metadata;
metadata.dist = this.createDist(name, version);
return {
name,
'dist-tags' : { latest: version },
versions : { [version]: { ...metadata }},
};
}
static createDist(name: string, version: string): any {
return {
shasum : '',
tarball : `http://registry.drpm.software/@drpm/${name}/-/${name}-${version}.tgz`
};
}
static updateMetadata(existingMetadata: any, newMetadata: any): any {
const {name, version} = newMetadata;
newMetadata.dist = !newMetadata.dist.tarball
? this.createDist(name, version)
: newMetadata.dist;
const tgzPath = join(DRPM_REGISTRY_DIR, name, `${name}-${version}.tgz`);
newMetadata.dist.shasum = this.calculateShasum1(tgzPath);
existingMetadata['dist-tags'].latest = version;
existingMetadata.versions[version] = newMetadata;
return existingMetadata;
}
static calculateShasum1(path: string): any {
const hash = createHash('sha1');
const stream = createReadStream(path);
return new Promise((resolve, reject) => {
stream.on('data', chunk => hash.update(chunk));
stream.on('end', () => {
const shasum = hash.digest('hex');
resolve(shasum);
});
stream.on('error', (error) => reject(error));
});
}
static isPrefixed(semver: string): boolean {
return DRPM_VERSION_PREFIXES.some((prefix: string) => semver.startsWith(prefix));
}
static findPrefix(prefixed: string): PrefixResponse {
const prefix = DRPM_VERSION_PREFIXES.find((prefix: string) => prefixed.startsWith(prefix));
return !prefix
? { version: prefixed, prefix: '' }
: { version: prefixed.slice(prefix.length), prefix };
}
static checkReqParams(params: Request['params']): string[][] {
return Object.entries(params).filter(([k, v]) => !v && k);
}
static routeFailure({code, status, error}: RouteFailureParams): RegistryResponse {
return { ok: false, code: code ?? 404, status: status ?? 'Not Found', error };
}
static routeSuccess({code, status, data}: RouteSuccessParams): RegistryResponse {
return { ok: true, code: code ?? 200, status: status ?? 'OK', data };
}
}