Files
smartnpm/ts/smartnpm.classes.npmregistry.ts
T

266 lines
8.0 KiB
TypeScript

import * as plugins from './smartnpm.plugins.js';
import * as paths from './smartnpm.paths.js';
// interfaces
import { type ISearchObject } from './smartnpm.interfaces.js';
// classes
import { NpmPackage } from './smartnpm.classes.npmpackage.js';
import { type ICacheDescriptor, RegistryCache } from './smartnpm.classes.registrycache.js';
export interface INpmRegistryConstructorOptions {
npmRegistryUrl?: string;
}
export class NpmRegistry {
public options: Required<INpmRegistryConstructorOptions>;
public registryCache: RegistryCache;
public smartFs = new plugins.smartfs.SmartFs(new plugins.smartfs.SmartFsProviderNode());
public smartFileFactory = plugins.smartfile.SmartFileFactory.nodeFs();
private searchDomain = 'https://api.npms.io/v2/search?q=';
constructor(optionsArg: INpmRegistryConstructorOptions = {}) {
const defaultOptions: Required<INpmRegistryConstructorOptions> = {
npmRegistryUrl: 'https://registry.npmjs.org',
};
this.options = {
...defaultOptions,
...optionsArg,
};
this.registryCache = new RegistryCache(this);
}
/**
* gets info about a package
* @param packageName
*/
public async getPackageInfo(packageName: string): Promise<NpmPackage> {
const fullMetadata = await plugins
.packageJson.default(packageName, {
registryUrl: this.options.npmRegistryUrl,
fullMetadata: true,
})
.catch((err) => {
console.log(err);
return null;
});
const versionData = await plugins.packageJson.default(packageName, {
registryUrl: this.options.npmRegistryUrl,
allVersions: true,
});
if (!fullMetadata) {
throw new Error(`Could not retrieve metadata for package ${packageName}.`);
}
const npmPackage = await NpmPackage.createFromFullMetadataAndVersionData(
this,
fullMetadata,
versionData as {
name: string;
'dist-tags': { [key: string]: string };
versions: { [key: string]: import('./smartnpm.classes.packageversion.js').IVersionData };
}
);
return npmPackage;
}
/**
* saves a package to disk
* @param packageName
* @param targetDir
*/
public async savePackageToDisk(packageName: string, targetDir: string): Promise<void> {
await this.smartFs.directory(paths.nogitDir).create();
const npmPackage = await this.getPackageInfo(packageName);
await npmPackage.saveToDisk(targetDir);
}
/**
* gets a file from a package as Smartfile
*/
public async getFileFromPackage(
packageNameArg: string,
filePathArg: string,
optionsArg?: {
distTag?: string;
version?: string;
}
): Promise<plugins.smartfile.SmartFile | null> {
// lets create a cache descriptor
const cacheDescriptor: ICacheDescriptor = {
registryUrl: this.options.npmRegistryUrl,
packageName: packageNameArg,
filePath: filePathArg,
distTag: optionsArg?.distTag,
version: optionsArg?.version,
};
// lets see if we have something cached
const cachedFile = await this.registryCache.getCachedFile(
cacheDescriptor
);
// lets handle both occasions
if (!cachedFile) {
const npmPackage = await this.getPackageInfo(packageNameArg);
if (!optionsArg?.version && !optionsArg?.distTag) {
const latestAvailable = npmPackage.allDistTags.find(
(packageArg) => packageArg.name === 'latest'
);
if (!latestAvailable) {
const version = npmPackage.getBestMatchingVersion('*');
optionsArg = version ? { version } : undefined;
}
}
const fileResult = await npmPackage.getFileFromPackage(filePathArg, optionsArg);
if (fileResult) {
this.registryCache.cacheSmartFile(cacheDescriptor, fileResult);
}
return fileResult;
} else {
return cachedFile;
}
}
public async getFilesFromPackage(
packageNameArg: string,
filePath: string,
optionsArg?: {
distTag?: string;
version?: string;
}
): Promise<plugins.smartfile.SmartFile[] | null> {
const npmPackage = await this.getPackageInfo(packageNameArg);
if (!optionsArg?.version && !optionsArg?.distTag) {
const latestAvailable = npmPackage.allDistTags.find(
(packageDistTagArg) => packageDistTagArg.name === 'latest'
);
if (!latestAvailable) {
const version = npmPackage.getBestMatchingVersion('*');
optionsArg = version ? { version } : undefined;
}
}
return npmPackage.getFilesFromPackage(filePath, optionsArg);
}
public async getPackageAsSmartfileVirtualDir(
packageNameArg: string
): Promise<plugins.smartfile.VirtualDirectory> {
/**
* TODO: rewrite as memory only
*/
const baseDir = plugins.path.join(paths.nogitDir, packageNameArg.replace('/', '__'));
await this.smartFs.directory(baseDir).create();
await this.savePackageToDisk(packageNameArg, baseDir);
const virtualDir = await this.smartFileFactory.virtualDirectoryFromPath(baseDir);
await this.smartFs.directory(baseDir).recursive().delete();
return virtualDir;
}
/**
* searches for a package on npm
* @param searchObjectArg
*/
public async searchOnNpm(searchObjectArg: ISearchObject) {
if (this.options.npmRegistryUrl !== 'https://registry.npmjs.org') {
throw Error(`cannot search registries other than registry.gitlab.com`);
}
let searchString = '';
const addToSearchString = (addStringArg: string) => {
searchString = `${searchString}+${addStringArg}`;
};
// name
if (searchObjectArg.name) {
searchString = `${searchObjectArg.name}`;
}
// metadata
if (searchObjectArg.author) {
addToSearchString(`author:${searchObjectArg.author}`);
}
if (searchObjectArg.maintainer) {
addToSearchString(`maintainer:${searchObjectArg.maintainer}`);
}
if (searchObjectArg.scope) {
addToSearchString(`scope:${searchObjectArg.scope}`);
}
// status
if (searchObjectArg.deprecated) {
if (searchObjectArg.deprecated === true) {
addToSearchString(`is:deprecated`);
} else {
addToSearchString(`not:deprecated`);
}
}
if (searchObjectArg.unstable) {
if (searchObjectArg.unstable === true) {
addToSearchString(`is:unstable`);
} else {
addToSearchString(`not:unstable`);
}
}
if (searchObjectArg.insecure) {
if (searchObjectArg.insecure === true) {
addToSearchString(`is:insecure`);
} else {
addToSearchString(`not:insecure`);
}
}
// search behaviour
if (searchObjectArg.boostExact) {
addToSearchString(`boost-exact:${searchObjectArg.boostExact}`);
}
if (searchObjectArg.scoreEffect) {
addToSearchString(`score-effect:${searchObjectArg.scoreEffect}`);
}
// analytics
if (searchObjectArg.qualityWeight) {
addToSearchString(`author:${searchObjectArg.qualityWeight}`);
}
if (searchObjectArg.popularityWeight) {
addToSearchString(`author:${searchObjectArg.popularityWeight}`);
}
if (searchObjectArg.maintenanceWeight) {
addToSearchString(`author:${searchObjectArg.maintenanceWeight}`);
}
console.log(
`info: Search on npm for ${plugins.consolecolor.coloredString(searchString, 'pink')}`
);
let body: {
results?: Array<{
package: {
name: string;
};
}>;
} | string | undefined;
try {
const response = await plugins.smartrequest.SmartRequest.create()
.url(this.searchDomain + searchString)
.get();
body = await response.json();
} catch {
// we do nothing
}
// lets create the packageArray
const packageArray: NpmPackage[] = [];
// if request failed just return it empty
if (!body || typeof body === 'string' || !Array.isArray(body.results)) {
return packageArray;
}
for (const packageSearchInfoArg of body.results) {
const npmPackage = await this.getPackageInfo(packageSearchInfoArg.package.name);
packageArray.push(npmPackage);
}
return packageArray;
}
}